#include "tls_socket_connection.h"

#include "./../description_module/description_util.h"
#include "./../log_module/log_public_module.h"
#include "socket_address.h"
#include "socket_channel.h"
#include "socket_connection.h"
#include "socket_description.h"
#include "socket_errno.h"
#include "tls_socket_operate.h"

#include <cerrno>
#include <cstdio>
#include <memory>
#include <sys/socket.h>

class TlsSocketChannel final : public SocketChannel
{
public:
    TlsSocketChannel(Loop * loop, const std::shared_ptr<Description> & description, TlsSocketConnection * connection);
    ~TlsSocketChannel() override;
    void OnErrorEvent(std::shared_ptr<SocketChannel> channel, std::shared_ptr<Description> description) override;
    void OnReadEvent(std::shared_ptr<SocketChannel> channel, std::shared_ptr<Description> description) override;
    void OnWriteEvent(std::shared_ptr<SocketChannel> channel, std::shared_ptr<Description> description) override;

private:
    TlsSocketConnection * m_connection;
};

TlsSocketChannel::TlsSocketChannel(
    Loop * loop,
    const std::shared_ptr<Description> & description,
    TlsSocketConnection * connection):SocketChannel(loop, description)
{
    m_connection = connection;
    LOG_SOCKET_MODULE("Create TlsSocketChannel(%p)",  this);
}

TlsSocketChannel::~TlsSocketChannel()
{
    LOG_SOCKET_MODULE("Release TlsSocketChannel(%p)",  this);
}

void TlsSocketChannel::OnErrorEvent(std::shared_ptr<SocketChannel> channel, std::shared_ptr<Description> description)
{
    LOG_SOCKET_MODULE("TlsSocketChannel(%p) OnErrorEvent description(%d) trigger error event", this, description->DescriptionData());
    m_connection->UpdateConnectState(SocketConnection::EmConnectStateDisconnected);
    auto cmd = SocketCommand::CreateCommand(SocketCommand::EmCmdDisconnect, m_connection->shared_from_this(), {});
    m_connection->Update(&cmd);
    channel->DelEvent();
}

void TlsSocketChannel::OnReadEvent(std::shared_ptr<SocketChannel> channel, std::shared_ptr<Description> description)
{
    if (m_connection->ConnectState() == TlsSocketConnection::EmConnectStateConnected)
    {
        while (true)
        {
            if (m_connection->m_read_buffer.BufferFreeSize() < 65535)
            {
                m_connection->m_read_buffer.ExpendBuffer();
            }
            int result = m_connection->m_tls_socket->Read(m_connection->m_read_buffer.BufferEnd(),
                                                          m_connection->m_read_buffer.BufferFreeSize());
            if (result < 0)
            {
                LOG_SOCKET_MODULE("TlsSocketChannel(%p) OnReadEvent description(%d) read result:%d errno:%d", this, description->DescriptionData(), result, errno);
                m_connection->UpdateConnectState(SocketConnection::EmConnectStateDisconnected);
                auto cmd = SocketCommand::CreateCommand(SocketCommand::EmCmdDisconnect, m_connection->shared_from_this(), {});
                m_connection->Update(&cmd);
                channel->DelEvent();
                break;
            }
            if (result > 0)
            {
                m_connection->m_read_buffer.MovePosition(result);
            }
            if (m_connection->m_read_buffer.BufferFreeSize() > 0)
            {
                break;
            }
        }
        if (m_connection->ConnectState() == SocketConnection::EmConnectStateConnected)
        {
            if (m_connection->m_read_buffer.BufferDataSize() > 0)
            {
                auto cmd = SocketCommand::CreateCommand(SocketCommand::EmCmdRead, m_connection->shared_from_this(), {});
                cmd.SetData(m_connection->m_read_buffer.BufferData(), m_connection->m_read_buffer.BufferDataSize());
                int length = m_connection->Update(&cmd) ? cmd.UseDataLength() : m_connection->m_read_buffer.BufferDataSize();
                m_connection->m_read_buffer.RemoveSize(length);
            }
            int event = EmIoIn | EmIoOneShot | EmIoRdHup;
            if (m_connection->m_write_buffer.BufferDataSize() > 0)
            {
                event |= EmIoOut;
            }
            channel->ModEvent(event);
        }
    }
}

void TlsSocketChannel::OnWriteEvent(std::shared_ptr<SocketChannel> channel, std::shared_ptr<Description> description)
{
    switch (m_connection->ConnectState())
    {
        case SocketConnection::EmConnectStateConnecting:
        {
            m_connection->UpdateConnectState(SocketConnection::EmConnectStateTlsHandShake);
            int result = m_connection->m_tls_socket->Connection(
                TlsSocketOperate::TlsTcpOption{
                    .m_host_name = m_connection->m_connection_option.m_host_name,
                    .m_ssl_key_log_file = m_connection->m_connection_option.m_ssl_key_log_file});
            if (result == -1)
            {
                channel->DelEvent();
                LOG_SOCKET_MODULE("TlsSocketChannel(%p) OnWriteEvent description(%d) update to disconnected", this, description->DescriptionData());

                m_connection->UpdateConnectState(SocketConnection::EmConnectStateDisconnected);
                auto cmd = SocketCommand::CreateCommand(SocketCommand::EmCmdDisconnect, m_connection->shared_from_this(), {});
                m_connection->Update(&cmd);
            }
            else if (result == 0)
            {
                channel->ModEvent(EmIoOut | EmIoRdHup | EmIoOneShot);
            }
            else
            {
                LOG_SOCKET_MODULE("TlsSocketChannel(%p) OnWriteEvent description(%d) update to connected", this, description->DescriptionData());
                m_connection->UpdateConnectState(SocketConnection::EmConnectStateConnected);
                channel->ModEvent(EmIoOneShot | EmIoRdHup);
                auto cmd = SocketCommand::CreateCommand(SocketCommand::EmCmdConnect, m_connection->shared_from_this(), {});
                m_connection->Update(&cmd);
            }
            break;
        }
        case SocketConnection::EmConnectStateTlsHandShake:
        {
            int result = m_connection->m_tls_socket->HandShake();
            if (result == -1)
            {
                channel->DelEvent();
                LOG_SOCKET_MODULE("TlsSocketChannel(%p) OnWriteEvent description(%d) update to disconnected", this, description->DescriptionData());
                m_connection->UpdateConnectState(SocketConnection::EmConnectStateDisconnected);
                auto cmd = SocketCommand::CreateCommand(SocketCommand::EmCmdError, m_connection->shared_from_this(), {});
                m_connection->Update(&cmd);
            }
            else if (result == 0)
            {
                channel->ModEvent(EmIoOut | EmIoOneShot | EmIoRdHup);
            }
            else
            {
                LOG_SOCKET_MODULE("TlsSocketChannel(%p) OnWriteEvent description(%d) update to connected", this, description->DescriptionData());
                m_connection->UpdateConnectState(SocketConnection::EmConnectStateConnected);
                channel->ModEvent(EmIoOneShot | EmIoRdHup);
                auto cmd = SocketCommand::CreateCommand(SocketCommand::EmCmdConnect, m_connection->shared_from_this(), {});
                m_connection->Update(&cmd);
            }
            break;
        }
        case SocketConnection::EmConnectStateConnected:
        {
            if (m_connection->m_write_buffer.BufferDataSize() > 0)
            {
                int result = m_connection->m_tls_socket->Write(m_connection->m_write_buffer.BufferData(),
                                                               m_connection->m_write_buffer.BufferDataSize());
                if (result >= 0)
                {
                    m_connection->m_write_buffer.RemoveSize(result);
                    if (m_connection->m_write_buffer.BufferDataSize() > 0)
                    {
                        channel->ModEvent(EmIoRdHup | EmIoOneShot | EmIoOut | EmIoIn);
                    }
                    else
                    {
                        channel->ModEvent(EmIoRdHup | EmIoOneShot | EmIoIn);
                    }
                }
                else
                {
                    channel->DelEvent();
                    m_connection->m_write_buffer.RemoveSize(m_connection->m_write_buffer.BufferDataSize());
                    m_connection->UpdateConnectState(SocketConnection::EmConnectStateDisconnected);
                    auto cmd = SocketCommand::CreateCommand(SocketCommand::EmCmdError, m_connection->shared_from_this(), {});
                    m_connection->Update(&cmd);
                }
            }
            else
            {
                channel->ModEvent(EmIoRdHup | EmIoOneShot | EmIoIn);
            }
            break;
        }
        default:
            break;
    }
}

TlsSocketConnection::TlsSocketConnection(Loop * loop) : SocketConnection(EmConnectionTypeTcpTls)
{
    m_loop = loop;
    LOG_SOCKET_MODULE("Create TlsSocketConnection(%s)", this);
}

TlsSocketConnection::~TlsSocketConnection()
{
    m_loop = nullptr;
    LOG_SOCKET_MODULE("Release TlsSocketConnection(%p)", this);
}

int TlsSocketConnection::OpenSocket(SocketAddress address, Option option)
{
    m_description = std::make_shared<SocketDescription>();
    m_description->CreateDescription();
    m_channel    = std::make_shared<TlsSocketChannel>(m_loop, m_description, this);
    m_tls_socket = std::make_shared<TlsSocketOperate>(m_description, TlsSocketOperate::EmOperateType::EmOperateTls);
    return EmSocketErrno_SUCCESS;
}

int TlsSocketConnection::AsyncConnect(SocketAddress address, Option option)
{
    if (ConnectState() == EmConnectStateInit)
    {
        auto self = shared_from_this();
        m_channel->Post([self, address, option]() {
            if (option.m_enable_nonblock)
            {
                DescriptionUtil::DescriptionBlockStatus(self->m_channel->DescriptionData(), 0);
            }
            self->m_connection_option = option;
            auto sai                  = address.NetAddress();
            int  result               = connect(self->m_description->DescriptionData(), reinterpret_cast<struct sockaddr *>(&sai), sizeof sai);
            if (result < 0 && errno != EINPROGRESS)
            {
                LOG_SOCKET_MODULE("TlsSocketConnection(%p) AsyncConnect description(%d) tcp connect error", self.get(), self->m_description->DescriptionData());
                self->UpdateConnectState(EmConnectStateDisconnected);
            }
            else
            {
                if (result == 0)
                {
                    LOG_SOCKET_MODULE("TlsSocketConnection(%p) AsyncConnect description(%d) tcp connected will tls handshake", self.get(), self->m_description->DescriptionData());
                    self->UpdateConnectState(EmConnectStateTlsHandShake);
                    self->m_tls_socket->Connection();
                }
                else
                {
                    LOG_SOCKET_MODULE("TlsSocketConnection(%p) AsyncConnect description(%d) tcp connecting", self.get(), self->m_description->DescriptionData());
                    self->UpdateConnectState(EmConnectStateConnecting);
                }
                self->m_channel->AddEvent(EmIoOut | EmIoOneShot | EmIoRdHup);
            }
        });
        return EmSocketErrno_SUCCESS;
    }
    return EmSocketErrno_CONNECT_FAILED;
}
int TlsSocketConnection::AsyncRead()
{
    if (ConnectState() == EmConnectStateConnected)
    {
        auto self = shared_from_this();
        m_channel->Post([self]() {
            int event = self->m_write_buffer.BufferDataSize() > 0 ? EmIoOut : 0;
            if (self->m_channel->Index() > 0)
            {
                self->m_channel->ModEvent(EmIoRdHup | EmIoOneShot | EmIoIn | event);
            }
            else
            {
                self->m_channel->AddEvent(EmIoRdHup | EmIoOneShot | EmIoIn | event);
            }
        });
        return EmSocketErrno_SUCCESS;
    }
    return EmSocketErrno_READ_FAILED;
}

int TlsSocketConnection::AsyncWrite(const uint8_t * data, int data_size)
{
    if (ConnectState() == EmConnectStateConnected)
    {
        m_write_buffer.PushDataToBuffer(data, data_size);
        auto self = shared_from_this();
        m_channel->Post([self]() {
            if (self->m_channel->Index() > 0)
            {
                self->m_channel->ModEvent(EmIoRdHup | EmIoOneShot | EmIoOut);
            }
            else
            {
                self->m_channel->AddEvent(EmIoRdHup | EmIoOneShot | EmIoOut);
            }
        });
        return EmSocketErrno_SUCCESS;
    }
    return EmSocketErrno_SEND_FAILED;
}

void TlsSocketConnection::Close()
{
    UpdateConnectState(EmConnectStateDisconnected);
    if (m_channel)
    {
        auto self = shared_from_this();
        auto channel = m_channel;
        m_channel = nullptr;
        channel->Post([channel, self] {
            if (channel->Index() > 0)
            {
                channel->DelEvent();
            }
            if (self->m_description)
            {
                self->m_description->CloseDescription();
                self->m_description = nullptr;
            }
        });
    }
}
